package net.sunofbeach.blog.service.impl;


import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.gson.Gson;
import com.wf.captcha.ArithmeticCaptcha;
import com.wf.captcha.GifCaptcha;
import com.wf.captcha.SpecCaptcha;
import com.wf.captcha.base.Captcha;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import net.sunofbeach.blog.mapper.RefreshTokenMapper;
import net.sunofbeach.blog.mapper.UserMapper;
import net.sunofbeach.blog.pojo.RefreshToken;
import net.sunofbeach.blog.pojo.Setting;
import net.sunofbeach.blog.pojo.User;
import net.sunofbeach.blog.pojo.query.UserQuery;
import net.sunofbeach.blog.pojo.vo.UserVO;
import net.sunofbeach.blog.response.Result;
import net.sunofbeach.blog.response.ResultEnum;
import net.sunofbeach.blog.service.SettingService;
import net.sunofbeach.blog.service.UserService;
import net.sunofbeach.blog.utils.*;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author Bing
 * @since 2021-11-24
 */
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Resource
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    @Resource
    private SettingService settingService;

    @Resource
    private RedisTemplate redisTemplate;


    private HttpServletRequest getRequest() {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        return requestAttributes.getRequest();
    }

    private HttpServletResponse getResponse() {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        return requestAttributes.getResponse();
    }


    /**
     * 初始化管理员账号
     *
     * @param user
     * @param request
     * @return
     */
    @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
    @Override
    public Result initManagerAccount(User user, HttpServletRequest request) {
        // 检查是否有初始化
        Setting managerAccountState = settingService.findOneByKey(Constants.Setting.MANAGER_ACCOUNT_INIT_STATE);
        if (managerAccountState != null) {
            return Result.fail("管理员账号已经初始化");
        }

        if (TextUtils.isEmpty(user.getUsername())) {
            return Result.fail("用户名不能为空");
        }
        if (TextUtils.isEmpty(user.getPassword())) {
            return Result.fail("密码不能为空");
        }
        if (TextUtils.isEmpty(user.getEmail())) {
            return Result.fail("邮箱不能为空");
        }
        // id 自动生成
        user.setRole(Constants.User.ROLE_ADMIN);
        user.setAvatar(Constants.User.DEFAULT_AVATAR);
        user.setState(Constants.User.STATE_DEFAULT);
        String remoteAddr = request.getRemoteAddr();
        String localAddr = request.getLocalAddr();
        log.info("remoteAddr ==> " + remoteAddr);
        log.info("localAddr ==> " + localAddr);
        user.setLoginIp(remoteAddr);
        user.setRegIp(localAddr);
        user.setCreateTime(LocalDateTime.now());
        user.setUpdateTime(LocalDateTime.now());
        // 对密码进行加密
        // 原密码
        String password = user.getPassword();
        // 加密
        user.setPassword(bCryptPasswordEncoder.encode(password));
        // 保存到数据库
        baseMapper.insert(user);
        // 更新已经添加的标记
        Setting setting = new Setting();
        setting.setSetKey(Constants.Setting.MANAGER_ACCOUNT_INIT_STATE);
        setting.setSetValue("1");
        setting.setCreateTime(LocalDateTime.now());
        setting.setUpdateTime(LocalDateTime.now());
        settingService.save(setting);
        return Result.ok("初始化成功");
    }

    /**
     * 验证码类型数组
     */
    public static final int[] captcha_font_type = {Captcha.FONT_1
            , Captcha.FONT_2
            , Captcha.FONT_3
            , Captcha.FONT_4
            , Captcha.FONT_5
            , Captcha.FONT_6
            , Captcha.FONT_7
            , Captcha.FONT_8
            , Captcha.FONT_9
            , Captcha.FONT_10};

    @Resource
    private Random random;

    /**
     * 创建图灵验证码
     *
     * @param response
     * @param captchaKey
     */
    @Override
    public void createCaptcha(HttpServletResponse response, String captchaKey) throws Exception {

        if (TextUtils.isEmpty(captchaKey) || captchaKey.length() < 13) {
            return;
        }
        long key;
        try {
            key = Long.parseLong(captchaKey);
        } catch (Exception e) {
            return;
        }
        // 可以用了
        // 设置请求头为输出图片类型
        response.setContentType("image/gif");
        response.setHeader("Pragma", "No-cache");
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expires", 0);

        // 设置图灵验证码的类型（多个类型）
        int captchaType = random.nextInt(3);
        Captcha targetCaptcha;
        int width = 120;
        int height = 40;

        if (captchaType == 0) {
            // 三个参数分别为宽、高、位数
            targetCaptcha = new SpecCaptcha(width, height, 4);
        } else if (captchaType == 1) {
            // gif 类型
            targetCaptcha = new GifCaptcha(width, height);
        } else {
            // 算术类型,几位数运算，默认是两位
            targetCaptcha = new ArithmeticCaptcha(width, height);
            targetCaptcha.setLen(2);
        }

        // 设置不同的类型图灵验证码
        int index = random.nextInt(captcha_font_type.length);
        log.info("captcha font type index ==> " + index);
        // 设置字体，上面已自定义为一个数组
        targetCaptcha.setFont(captcha_font_type[index]);
        // 设置类型，纯数字、纯字母、字母数字混合
        targetCaptcha.setCharType(Captcha.TYPE_DEFAULT);

        // 获取验证码内容
        String content = targetCaptcha.text().toLowerCase();
        log.info("captcha content ==> " + content);

        // 保存到 redis, 有效时长 10 分钟
        redisTemplate.opsForValue().set(Constants.User.KEY_CAPTCHA_CONTENT + key, content, 10, TimeUnit.MINUTES);
        // 输出图片流
        targetCaptcha.out(response.getOutputStream());

    }


    @Resource
    private TaskService taskService;

    /**
     * 发送邮件验证码
     * <p>
     * 使用场景： 注册、找回密码、修改邮箱（会输入新的密码）
     * 注册(register)： 如果已经注册，提示 该邮箱已经注册；
     * 找回密码(forget)： 如果没有注册，提示 该邮箱未注册
     * 修改邮箱(update)（新的邮箱）： 如果已经注册，提示 该邮箱已经被注册
     * 解决： 根据不类型的场景，执行对应的业务逻辑。
     *
     * @param request      校验 IP
     * @param type         不同类型使用场景：注册 register、找回密码 forget、修改邮箱 update
     * @param emailAddress 邮箱地址
     * @return
     */
    @Override
    public Result sendEmail(HttpServletRequest request, String type, String emailAddress) {
        // 检查输入的邮箱地址是否为空
        if (TextUtils.isEmpty(emailAddress)) {
            return Result.fail().message("邮箱地址不能为空");
        }
        // 检查输入的邮箱地址是否正确
        if (!TextUtils.isEmailAddressOk(emailAddress)) {
            return Result.fail().message("邮箱地址格式不正确");
        }
        // 注册 register、修改邮箱 update 时，查数据库邮箱是否已存在，存在不能注册，不存在发送邮件验证码
        if (Constants.Email.REGISTER.equals(type) || Constants.Email.UPDATE.equals(type)) {
            // 查数据库，校验邮箱地址是否已被注册
            if (this.checkEmail(emailAddress)) {
                return Result.fail().message("该邮箱已被注册");
            }
        }
        // 找回密码，检查邮箱是否已存在，未注册不发送邮件验证码
        if (Constants.Email.FORGET.equals(type)) {
            if (!this.checkEmail(emailAddress)) {
                return Result.fail().message("该邮箱未注册");
            }
        }
        // 1、防止暴力发送不断地发送：同一个邮箱，间隔要超过 1分钟 发送一次，同一个 IP，1小时内最多只能发10次（如果是短信，最多只能发送5次）
        String remoteAddr = request.getRemoteAddr();
        log.info("sendEmail ==> ip ==> " + remoteAddr);
        // 将IP地址的：号替换成_
        if (remoteAddr != null) {
            remoteAddr = remoteAddr.replaceAll(":", "_");
        }
        log.info("sendEmail ==> ip2 ==> " + remoteAddr);
        // 取出来，如果没有，通过
        Integer ipSendTimeValue = (Integer) redisTemplate.opsForValue().get(Constants.User.KEY_EMAIL_SEND_IP + remoteAddr);
        Integer ipSendTime;
        if (ipSendTimeValue != null) {
            ipSendTime = ipSendTimeValue;
        } else {
            ipSendTime = 1;
        }
        // 10为限制发送的次数
        if (ipSendTime > 10) {
            return Result.fail().message("请不要频繁发送验证码");
        }
        Object hasEmailSend = redisTemplate.opsForValue().get(Constants.User.KEY_EMAIL_SEND_ADDRESS + emailAddress);
        if (hasEmailSend != null) {
            return Result.fail().message("请不要频繁发送验证码");
        }
        // 2、检查邮箱地址是否正确
        boolean isEmailFormatOk = TextUtils.isEmailAddressOk(emailAddress);
        if (!isEmailFormatOk) {
            return Result.fail().message("邮箱地址格式不正确");
        }
        // 3、检查邮箱是否已存在
        //生成六位随机数字
        String code = RandomUtils.getSixBitRandom();
        log.info("sendEmail ==> code ==> " + code);
        // 3、调用下面的方法，发送邮件验证码
        try {
            // 调用异步方法发送邮件
            taskService.sendEmailVerifyCode(emailAddress, code);
        } catch (Exception e) {
            return Result.fail().message("验证码发送失败，请稍后重试");
        }
        // 4、做记录: 发送记录、code
        if (ipSendTime == null) {
            ipSendTime = 0;
        }
        ipSendTime++;
        // 5、设置 IP key 有效时长为1个小时10次（同一个 IP，1小时内最多只能发10次（如果是短信，最多只能发送5次））
        redisTemplate.opsForValue().set(Constants.User.KEY_EMAIL_SEND_IP + remoteAddr, ipSendTime, 60, TimeUnit.MINUTES);
        // 6、设置 同一个邮箱，间隔要超过 1分钟发送一次(true 为占位字符串）
        redisTemplate.opsForValue().set(Constants.User.KEY_EMAIL_SEND_ADDRESS + emailAddress, "true", 1, TimeUnit.MINUTES);
        // 7、将验证码存入 redis，参数：自定义常量key、邮箱地址、生成的验证码、过期时间 10 分钟
        redisTemplate.opsForValue().set(Constants.User.KEY_EMAIL_CODE_CONTENT + emailAddress, code, 10, TimeUnit.MINUTES);

        // 返回结果给前端
        return Result.ok().message("验证码发送成功");
    }

    /**
     * 用户注册
     *
     * @param user        用户
     * @param emailCode   邮箱验证码
     * @param captchaCode 图灵验证码
     * @param captchaKey  图灵验证码key
     * @param request     用于传IP地址
     * @return
     */
    @Override
    public Result register(User user, String emailCode, String captchaCode, String captchaKey, HttpServletRequest request) {
        String username = user.getUsername();
        String email = user.getEmail();

        //1、校验用户名
        // 校验用户名不能为空
        if (TextUtils.isEmpty(username)) {
            return Result.fail().message("用户名不能为空");
        }
        // 校验用户名是否已被注册（查数据库）
        if (this.checkUsername(username)) {
            return Result.fail().message("该用户名已被注册");
        }
        // 2、检查邮箱格式是否正确
        // 校验邮箱地址不能为空
        if (TextUtils.isEmpty(email)) {
            return Result.fail().message("邮箱地址不能为空");
        }
        // 校验邮箱地址是否正确
        if (!TextUtils.isEmailAddressOk(email)) {
            return Result.fail().message("邮箱地址格式不正确");
        }
        // 3、检查该邮箱是否已经注册
        if (this.checkEmail(email)) {
            return Result.fail().message("该邮箱已被注册");
        }
        // 4、校验邮箱验证码是否正确
        String emailVerifyCode = (String) redisTemplate.opsForValue().get(Constants.User.KEY_EMAIL_CODE_CONTENT + email);
        if (TextUtils.isEmpty(emailVerifyCode)) {
            return Result.fail().message("邮箱验证码已过期");
        }
        if (!emailVerifyCode.equals(emailCode)) {
            return Result.fail().message("邮箱验证码不正确");
        } else {
            // 验证通过，删除redis邮箱验证码key
            redisTemplate.delete(Constants.User.KEY_EMAIL_CODE_CONTENT + email);
        }

        //5、检查图灵验证码是否正确
        String captchaVerifyCode = (String) redisTemplate.opsForValue().get(Constants.User.KEY_CAPTCHA_CONTENT + captchaKey);
        if (TextUtils.isEmpty(captchaVerifyCode)) {
            return Result.fail().message("人类验证码已过期");
        }
        if (!captchaVerifyCode.equals(captchaCode)) {
            return Result.fail().message("人类验证码不正确");
        } else {
            // 验证通过，删除redis图灵验证码 key
            redisTemplate.delete(Constants.User.KEY_CAPTCHA_CONTENT + captchaKey);
        }

        // 达到可以注册的条件
        //6、对密码进行加密
        String password = user.getPassword();
        if (TextUtils.isEmpty(password)) {
            return Result.fail().message("密码不能为空");
        }
        user.setPassword(bCryptPasswordEncoder.encode(password));

        //7、补全数据
        //包括：注册IP、登录IP、角色、头像、创建时间、更新时间
        String ipAddress = request.getRemoteAddr();
        user.setUsername(username);
        user.setEmail(email);
        user.setRegIp(ipAddress);
        user.setLoginIp(ipAddress);
        user.setAvatar(Constants.User.DEFAULT_AVATAR);
        user.setState(Constants.User.STATE_DEFAULT);
        user.setUpdateTime(LocalDateTime.now());
        user.setCreateTime(LocalDateTime.now());
        user.setRole(Constants.User.ROLE_NORMAL);
        //8、保存到数据库中
        baseMapper.insert(user);
        //9、返回结果
        return Result.ok().message("注册成功");
    }

    @Resource
    private RefreshTokenMapper refreshTokenMapper;

    /**
     * 用户登录
     *
     * @param captcha    图灵验证码
     * @param captchaKey 图灵key
     * @param user       用户
     * @param request
     * @param response
     * @return
     */
    @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
    @Override
    public Result doLogin(String captcha, String captchaKey, User user, HttpServletRequest request, HttpServletResponse response) {
        // 获取图灵验证码
        String captchaValue = (String) redisTemplate.opsForValue().get(Constants.User.KEY_CAPTCHA_CONTENT + captchaKey);
        if (!captcha.equals(captchaValue)) {
            return Result.fail().message("人类验证码不正确");
        }
        // 验证码成功，删除 redis 里的验证码
        redisTemplate.delete(Constants.User.KEY_CAPTCHA_CONTENT + captchaKey);
        // 用户输入的可能是邮箱，也可能是用户名，这里假设是用户名
        String username = user.getUsername();
        if (TextUtils.isEmpty(username)) {
            return Result.fail().message("账号不能为空");
        }
        String password = user.getPassword();
        if (TextUtils.isEmpty(password)) {
            return Result.fail().message("密码不能为空");
        }
        // 先根据用户名查找用户
        User userFromDb = baseMapper.selectOne(new QueryWrapper<User>().eq("username", username));
        if (userFromDb == null) {
            // 根据用户名查不到用户，这里根据用户的输入查询邮箱，通过邮箱查找用户，因为用户有可能输入的不是用户名，是邮箱
            baseMapper.selectOne(new QueryWrapper<User>().eq("email", username));
        }
        if (userFromDb == null) {
            return Result.fail().message("用户名或密码不正确");
        }
        // 用户存在
        // 比对密码
        boolean matches = bCryptPasswordEncoder.matches(password, userFromDb.getPassword());
        if (!matches) {
            return Result.fail().message("用户名或密码不正确");
        }
        // 密码正确
        // 判断用户状态，如果是非正常状态，则返回结果
        if (!Constants.User.STATE_DEFAULT.equals(userFromDb.getState())) {
            // 该账号已被禁用
            return Result.build(null, ResultEnum.ACCOUNT_DISABLED);
        }
        // 生成token
        createToken(response, userFromDb);
        return Result.ok().message("登录成功");
    }

    /**
     * 生成 token
     *
     * @param response
     * @param userFromDb
     * @return tokenKey
     */
    private String createToken(HttpServletResponse response, User userFromDb) {
        // 根据用户ID删除之前所有 refreshToken
        refreshTokenMapper.delete(new QueryWrapper<RefreshToken>().in("user_id", userFromDb.getId()));
        // 生成 token，设置载荷用户信息
        Map<String, Object> claims = ClaimsUtils.sobUser2Claims(userFromDb);
        // token 默认有效时间为2个小时（JwtUtil 中定义）
        String token = JwtUtil.createToken(claims);
        // 返回token的md5值，token会保存到redis里
        // 前端访问的时候，携带token的md5key，从redis中获取即可
        String tokenKey = DigestUtils.md5DigestAsHex(token.getBytes());
        // 保存token到redis里，有效期为2个小时
        redisTemplate.opsForValue().set(Constants.User.KEY_TOKEN + tokenKey, token, 2, TimeUnit.HOURS);
        // 使用工具类，把tokenKey写到cookies里
        // 这个要动态获取，可以从request里获取
        CookieUtils.setUpCookie(response, Constants.User.COOKIE_TOKEN_KEY, tokenKey);
        // 生成 refreshToken，有效期 1个月（单位毫秒）
        String refreshTokenValue = JwtUtil.createRefreshToken(userFromDb.getId(), (long) (60 * 60 * 24 * 30 * 1000));
        // 将 refreshToken 保存到数据库，字段：refreshToken、tokenKey，用户ID，创建时间，更新时间
        RefreshToken refreshToken = new RefreshToken();
        refreshToken.setRefreshToken(refreshTokenValue);
        refreshToken.setTokenKey(tokenKey);
        refreshToken.setUserId(userFromDb.getId());
        refreshToken.setCreateTime(LocalDateTime.now());
        refreshToken.setUpdateTime(LocalDateTime.now());
        // 保存到数据库
        refreshTokenMapper.insert(refreshToken);
        return tokenKey;
    }


    /**
     * 检查登录状态
     * 本质，通过用户携带的token_key 检查用户是否有登录，如果登录，返回用户信息
     *
     * @return
     */
    @Override
    public User checkSobUser() {
        // 拿到 request 和 response
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        HttpServletResponse response = requestAttributes.getResponse();

        // 拿到 token_key
        String tokenKey = CookieUtils.getCookie(request, Constants.User.COOKIE_TOKEN_KEY);
        // 解析 token
        User user = parseByTokenKey(tokenKey);
        if (user == null) {
            // 说明解析token出错了，token过期
            // 1、去mysql查询refreshToken
            RefreshToken refreshToken = refreshTokenMapper.selectOne(new QueryWrapper<RefreshToken>().eq("token_key", tokenKey));
            // 2、如果不存在，就是当前访问没有登录，提示用户登录
            if (refreshToken == null) {
                return null;
            }
            // 3、如果存在，就解析refreshToken
            try {
                JwtUtil.parseJWT(refreshToken.getRefreshToken());
                // 4、如果 refreshToken 有效，创建新的 token，和新的 refreshToken
                String userId = refreshToken.getUserId();
                // 根据ID查询出数据库中的用户
                User userFromDb = baseMapper.selectById(userId);
                // 删除 refreshToken 的记录
                //refreshTokenMapper.deleteById(refreshToken.getId());
                // 生成新 token
                String newTokenKey = createToken(response, userFromDb);
                // 返回新创建解析后的 token
                return parseByTokenKey(newTokenKey);
            } catch (Exception ex) {
                // 5、如果 refreshToken 过期了，就当前访问没有登录，提示用户登录
                ex.printStackTrace();
            }
        }
        return user;
    }


    @Resource
    private Gson gson;

    /**
     * 获取作者信息
     *
     * @param userId
     * @return
     */
    @Override
    public Result getUserInfo(String userId) {
        // 从数据库里获取用户
        User user = baseMapper.selectById(userId);
        if (user == null) {
            // 如果不存在，返回不存在
            return Result.fail().message("用户不存在");
        }
        // 如果存在，复制对象，清空密码、Email、登录ID、注册IP
        String userJson = gson.toJson(user);
        User newUser = gson.fromJson(userJson, user.getClass());
        newUser.setPassword("");
        newUser.setEmail("");
        newUser.setLoginIp("");
        newUser.setRegIp("");
        // 返回结果
        return Result.ok(newUser).message("获取成功");
    }


    /**
     * 解析 token
     *
     * @param tokenKey
     * @return
     */
    private User parseByTokenKey(String tokenKey) {
        // 获取时要加前缀，因创建时加了前缀
        String token = (String) redisTemplate.opsForValue().get(Constants.User.KEY_TOKEN + tokenKey);
        if (token != null) {
            try {
                Claims claims = JwtUtil.parseJWT(token);
                return ClaimsUtils.claims2SobUser(claims);
            } catch (Exception e) {
                return null;
            }
        }
        return null;
    }


    /**
     * 检查用户名是否已被注册
     * 返回 ture  或 false / 内部调用
     *
     * @param username
     * @return
     */
    public boolean checkUsername(String username) {
        Integer count = baseMapper.selectCount(new QueryWrapper<User>().eq("username", username));
        return count > 0;
    }

    /**
     * 检查邮箱是否被注册
     * 返回 ture  或 false / 内部调用
     *
     * @param email
     * @return
     */
    public boolean checkEmail(String email) {
        Integer count = baseMapper.selectCount(new QueryWrapper<User>().eq("email", email));
        return count > 0;
    }

    /**
     * 检查邮箱是否被注册/返回给前端
     *
     * @param email
     * @return
     */
    @Override
    public Result checkEmailToFront(String email) {
        Integer count = baseMapper.selectCount(new QueryWrapper<User>().eq("email", email));
        return count > 0 ? Result.ok().message("该邮箱已经注册") : Result.fail().message("该邮箱未注册");
    }


    /**
     * 检查用户名是否被注册
     * 返回给前端
     *
     * @param username
     * @return
     */
    @Override
    public Result checkUsernameToFront(String username) {
        Integer count = baseMapper.selectCount(new QueryWrapper<User>().eq("username", username));
        return count > 0 ? Result.ok().message("该用户名已存在") : Result.fail().message("该用户名未注册");
    }

    /**
     * 更新用户信息
     *
     * @param request
     * @param user
     * @return
     */
    @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
    @Override
    public Result updateUserInfo(HttpServletRequest request, User user) {
        // 校验权限，调用解析 token 方法，解析 token 里的 user
        // 用户只能修改自己的信息
        User userFromTokenKey = checkSobUser();
        if (userFromTokenKey == null) {
            // 返回 账号未登录
            return Result.build(null, ResultEnum.ACCOUNT_NOT_LOGIN);
        }
        // token 校验通过，查询数据库用户
        User userFromDb = baseMapper.selectById(userFromTokenKey.getId());

        // 判断数据库用户ID与当前登录用户ID是否一致,如果一致可以进行修改操作
        if (!userFromDb.getId().equals(user.getId())) {
            // 返回 权限不足
            return Result.build(null, ResultEnum.PERMISSION_DENIED);
        }
        // 可以进行修改
        // 可以修改的项
        // 用户名
        String username = user.getUsername();
        if (!TextUtils.isEmpty(username) && !username.equals(userFromTokenKey.getUsername())) {
            // 调用方法检查用户是否已注册,返回如果是布尔类型 true 为已注册,false 为未注册
            if (checkUsername(username)) {
                return Result.fail().message("该用户名已被占用");
            }
            userFromDb.setUsername(username);
        }
        // 头像
        if (!TextUtils.isEmpty(user.getAvatar())) {
            userFromDb.setAvatar(user.getAvatar());
        }
        // 签名(可为空)
        userFromDb.setSign(user.getSign());
        userFromDb.setUpdateTime(LocalDateTime.now());
        baseMapper.updateById(userFromDb);
        // 删除 redis 里的 token，下一次请求需要解析 token ,就会根据 refreshToken 重新创建一个 token，显示新的数据。
        String tokenKey = CookieUtils.getCookie(request, Constants.User.COOKIE_TOKEN_KEY);
        // 要加前缀，否则删除不了
        redisTemplate.delete(Constants.User.KEY_TOKEN + tokenKey);
        return Result.ok().message("信息更新成功!");
    }


    /**
     * 删除用户
     * 并不是真的删除，而是修改状态值
     * 需要管理员权限
     *
     * @param userId
     * @return
     */
    @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
    @Override
    public Result deleteUserById(String userId) {
        // 检验当前操作用户的身份
        User currentUser = checkSobUser();
        if (currentUser == null) {
            // 账号未登录
            return Result.build(null, ResultEnum.ACCOUNT_NOT_LOGIN);
        }
        // 判断角色，拥有 role_admin 角色可以操作
        if (!Constants.User.ROLE_ADMIN.equals(currentUser.getRole())) {
            // 权限不足
            return Result.build(null, ResultEnum.PERMISSION_DENIED);
        }
        // 可以删除用户了( 更新用户状态为 0 ）
        User user = baseMapper.selectById(userId);
        user.setState(Constants.User.STATE_DELETE);
        user.setUpdateTime(LocalDateTime.now());
        int result = baseMapper.updateById(user);

        return result > 0 ? Result.ok().message("删除成功") : Result.fail().message("用户不存在");
    }


    /**
     * 用户修改密码、找回密码
     *
     * @param verifyCode
     * @param user
     * @return
     */
    @Override
    public Result updateUserPassword(String verifyCode, User user) {
        // 检查邮箱是否填写
        String email = user.getEmail();
        if (TextUtils.isEmpty(email)) {
            return Result.fail().message("邮箱不能为空");
        }
        // 校验邮箱地址是否正确
        if (!TextUtils.isEmailAddressOk(email)) {
            return Result.fail().message("邮箱地址格式不正确");
        }
        // 检查用户输入的密码
        String password = user.getPassword();
        if (TextUtils.isEmpty(password)) {
            return Result.fail().message("密码不能为空");
        }
        // 根据邮箱查找用户
        User userFromDb = baseMapper.selectOne(new QueryWrapper<User>().eq("email", email));
        if (userFromDb == null) {
            return Result.fail().message("该邮箱未注册");
        }
        // 根据邮箱去 redis 里拿验证码
        // 进行对比
        String redisVerifyCode = (String) redisTemplate.opsForValue().get(Constants.User.KEY_EMAIL_CODE_CONTENT + email);
        if (redisVerifyCode == null || !redisVerifyCode.equals(verifyCode)) {
            return Result.fail().message("验证码错误");
        }
        // 删除redis里的key
        redisTemplate.delete(Constants.User.KEY_EMAIL_CODE_CONTENT + email);

        // 修改数据库用户密码、更新时间
        userFromDb.setPassword(bCryptPasswordEncoder.encode(password));
        userFromDb.setUpdateTime(LocalDateTime.now());
        // 保存数据
        int result = baseMapper.updateById(userFromDb);
        // 返回结果
        return result > 0 ? Result.ok().message("密码修改成功") : Result.fail().message("密码修改失败");
    }


    /**
     * 用户更新邮箱
     *
     * @param email
     * @param verifyCode
     * @return
     */
    @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
    @Override
    public Result updateEmail(String email, String verifyCode) {
        //1、确保用户已经登录了（获取token中的user )
        User user = this.checkSobUser();
        // 账号没有登录
        if (user == null) {
            return Result.build(null, ResultEnum.ACCOUNT_NOT_LOGIN);
        }
        //2、对比验证码，确保新的邮箱地址是属于当前用户的( 邮箱验证码 key + email )
        String redisVerifyCode = (String) redisTemplate.opsForValue().get(Constants.User.KEY_EMAIL_CODE_CONTENT + email);
        if (TextUtils.isEmpty(redisVerifyCode) || !redisVerifyCode.equals(verifyCode)) {
            return Result.fail().message("验证码错误");
        }
        // 验证码正确，删除验证码
        redisTemplate.delete(Constants.User.KEY_EMAIL_CODE_CONTENT + email);
        // 可以修改邮箱
        // 从用户验证信息中获取ID查询数据库用户
        User userFromDb = baseMapper.selectById(user.getId());
        userFromDb.setEmail(email);
        userFromDb.setUpdateTime(LocalDateTime.now());

        // 保存数据
        int result = baseMapper.updateById(userFromDb);
        // 返回结果
        return result > 0 ? Result.ok().message("邮箱修改成功") : Result.fail().message("邮箱修改失败");
    }


    /**
     * 退出登录
     * 1、拿到 token_key
     * 2、删除redis里对应的token
     * 3、删除mysql里对应的 refreshToken
     * 4、删除cookie里的token_key
     *
     * @return
     */
    @Override
    public Result doLogout() {
        // 拿到 token_key
        String tokenKey = CookieUtils.getCookie(getRequest(), Constants.User.COOKIE_TOKEN_KEY);
        if (TextUtils.isEmpty(tokenKey)) {
            // 账号未登录
            return Result.build(ResultEnum.ACCOUNT_NOT_LOGIN);
        }
        // 删除 redis 里的 token
        redisTemplate.delete(Constants.User.KEY_TOKEN + tokenKey);
        // 删除 mysql 里的 refreshToken
        refreshTokenMapper.delete(new QueryWrapper<RefreshToken>().eq("token_key", tokenKey));
        // 删除 cookie 里的 token_key
        CookieUtils.deleteCookie(getResponse(), Constants.User.COOKIE_TOKEN_KEY);
        return Result.ok().message("退出成功！");
    }


    /**
     * 条件分页查询用户列表
     *
     * @param query
     * @return
     */
    @Override
    public IPage<UserVO> selectPage(Page<UserVO> pageParam, UserQuery query) {

        // 对条件进行非空判断
        QueryWrapper<UserVO> wrapper = new QueryWrapper<>();

        if (!StringUtils.isEmpty(query.getUsername())) {
            wrapper.like("username", query.getUsername());
        }

        if (!StringUtils.isEmpty(query.getId())) {
            wrapper.like("id", query.getId());
        }
        if (!StringUtils.isEmpty(query.getEmail())) {
            wrapper.like("email", query.getEmail());
        }

        if (!StringUtils.isEmpty(query.getLoginIp())) {
            wrapper.like("login_ip", query.getLoginIp());
        }

        if (!StringUtils.isEmpty(query.getRole())) {
            wrapper.like("role", query.getRole());
        }

        if (!StringUtils.isEmpty(query.getState())) {
            wrapper.like("state", query.getState());
        }
        if (!StringUtils.isEmpty(query.getSign())) {
            wrapper.like("sign", query.getSign());
        }

        if (!StringUtils.isEmpty(query.getBegin())) {
            wrapper.ge("create_time", query.getBegin());
        }
        if (!StringUtils.isEmpty(query.getEnd())) {
            wrapper.le("create_time", query.getEnd());
        }

        wrapper.orderByDesc("state", query.getState())
                .orderByDesc("create_time");

        // 组装分页
        List<UserVO> records = baseMapper.selectPageByUserVO(pageParam, wrapper);

        // 将 records 设置到 pageParam 中
        return pageParam.setRecords(records);
    }


    /**
     * 通过Token获取用户信息
     *
     * @return
     */
    @Override
    public Result parseToken() {
        User user = checkSobUser();
        if (user == null) {
            return Result.fail().message("用户未登录");
        }
        return Result.ok(user);
    }


    /**
     * 管理员重置用户密码
     *
     * @param userId
     * @param password
     * @return
     */
    @Override
    public Result resetPassword(String userId, String password) {
        // 检验当前操作用户的身份
        User currentUser = checkSobUser();
        if (currentUser == null) {
            // 账号未登录
            return Result.build(null, ResultEnum.ACCOUNT_NOT_LOGIN);
        }
        // 判断角色，拥有 role_admin 角色可以操作
        if (!Constants.User.ROLE_ADMIN.equals(currentUser.getRole())) {
            // 权限不足
            return Result.build(null, ResultEnum.PERMISSION_DENIED);
        }
        // 查询出来
        User user = baseMapper.selectById(userId);
        // 判断是否存在
        if (user == null) {
            return Result.fail().message("用户不存在");
        }
        // 密码进行加密
        user.setPassword(bCryptPasswordEncoder.encode(password));
        int result = baseMapper.updateById(user);
        // 处理结果
        return result > 0 ? Result.ok().message("重置密码成功") : Result.fail().message("重置密码失败");
    }

}




























